package org.eliza.base.coroutinesx

import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.channels.ticker
/*
*
*
* suspend方法和CoroutineScope扩展方法的取舍
Kotlin中，有两个约定俗成的东西
    每一个声明为CoroutineScope的扩展方法的方法，都会马上返回，但是会并发地执行扩展方法指定的内容
        这也是runBlocking不是CoroutineScope的扩展方法的原因之一
    每一个仅声明为suspend的方法，会等待其内部逻辑完成后再返回给调用者
        *suspend方法就应该在所有任务都完成后再返回。如果在suspend方法内部有需要并发执行的内容，那就应该等待他们都完成后再返回，
        * 此时可以使用coroutineScope{}，而不是在方法签名上加上CoroutineScope扩展。
*
*
* */



private fun SendChannel() = runBlocking {
    println("=== SendChannel ===")
    val channel = Channel<String>()
    val launch = launch { sendString(channel, "foo ", 200L) }
    launch { sendString(channel, "BAR!", 500L) }
    launch.invokeOnCompletion {
        println("foo end")
    }
    repeat(6) { // receive first six
        println("receive -> ${channel.receive()}")
    }
    coroutineContext.cancelChildren() // cancel all children to let main finish
}

//suspend fun
private suspend fun sendString(channel: SendChannel<String>, s: String, time: Long) {
    while (true) {
        delay(time)
        channel.send("$s : $time")
    }
}


//==========


private data class Ball(var hits: Int)

private fun sendData() = runBlocking {
    println("=== sendData ===")

    // 无缓冲
    val table = Channel<Ball>() // a shared table

    // 两个协程相互制约
    launch { player("ping ==>", table) }
    launch { player("pong    ", table) }
    table.send(Ball(0)) // serve the ball
    delay(1000) // delay 1 second
    coroutineContext.cancelChildren() // game over, cancel them
}

private suspend fun player(name: String, table: Channel<Ball>) {
    for (ball in table) { // receive the ball in a loop
        ball.hits++
        println("$name $ball")
        delay(300) // wait a bit
        table.send(ball) // send the ball back
    }
}

//==========
@OptIn(ObsoleteCoroutinesApi::class)
private fun withTimeoutOrNull() = runBlocking<Unit> {
    println("=== withTimeoutOrNull ===")

    val tickerChannel = ticker(delayMillis = 200, initialDelayMillis = 0) // create a ticker channel

    var nextElement = withTimeoutOrNull(1) {
        tickerChannel.receive()
        1
    }
    println("=======> available immediately:                         ======> $nextElement") // kotlin.Unit

    nextElement = withTimeoutOrNull(100) {
        tickerChannel.receive()
        2
    } // all subsequent elements have 200ms delay
    println("=======> not ready in 100 ms:                           ======> $nextElement")//null

    nextElement = withTimeoutOrNull(120) {
        tickerChannel.receive()
        3
    } //220
    println("=======> ready in 200 ms:                               ======> $nextElement") //kotlin.Unit

    // Emulate large consumption delays
    println("=======> Consumer pauses for 300ms <======")
    delay(300)  //520  --> 120
    // Next element is available immediately
    nextElement = withTimeoutOrNull(1) {
        tickerChannel.receive()
        4
    }
    println("available immediately after large consumer delay: $nextElement") //kotlin.Unit
    // Note that the pause between `receive` calls is taken into account and next element arrives faster
    nextElement = withTimeoutOrNull(120) {
        tickerChannel.receive()
        5
    } //220
    println("ready in 100ms after consumer pause in 300ms:     $nextElement") //kotlin.Unit

    tickerChannel.cancel() // indicate that no more elements are needed
}


fun main() {
//    SendChannel()
//    sendData()
    withTimeoutOrNull()

}